以下代碼是Emit版本,我把C#對應IL部分都寫在註解。
public static class DemoExtension
{
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql) where T : new()
{
using (var command = cnn.CreateCommand())
{
command.CommandText = sql;
using (var reader = command.ExecuteReader())
{
var func = GetTypeDeserializerImpl(typeof(T), reader);
while (reader.Read())
{
var result = func(reader as DbDataReader);
yield return result is T ? (T)result : default(T);
}
}
}
}
private static Func<DbDataReader, object> GetTypeDeserializerImpl(Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false)
{
var returnType = type.IsValueType ? typeof(object) : type;
var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true);
var il = dm.GetILGenerator();
//C# : User user = new User();
//IL :
//IL_0001: newobj
//IL_0006: stloc.0
var constructor = returnType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)[0]; //這邊簡化成只會有預設constructor
il.Emit(OpCodes.Newobj, constructor);
var returnValueLocal = il.DeclareLocal(type);
il.Emit(OpCodes.Stloc, returnValueLocal); //User user = new User();
// C# :
//object value = default(object);
// IL :
//IL_0007: ldnull
//IL_0008: stloc.1 // value
var valueLoacl = il.DeclareLocal(typeof(object));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc, valueLoacl);
int index = startBound;
var getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int))
.Select(p => p.GetGetMethod()).First();
foreach (var p in type.GetProperties())
{
//C# : value = P_0[0];
//IL:
//IL_0009: ldarg.0
//IL_000A: ldc.i4.0
//IL_000B: callvirt System.Data.IDataRecord.get_Item
//IL_0010: stloc.1 // value
il.Emit(OpCodes.Ldarg_0); //取得reader參數
EmitInt32(il, index);
il.Emit(OpCodes.Callvirt, getItem);
il.Emit(OpCodes.Stloc, valueLoacl);
//C#: if (!(value is DBNull)) user.Name = (string)value;
//IL:
// IL_0011: ldloc.1 // value
// IL_0012: isinst System.DBNull
// IL_0017: ldnull
// IL_0018: cgt.un
// IL_001A: ldc.i4.0
// IL_001B: ceq
// IL_001D: stloc.2
// IL_001E: ldloc.2
// IL_001F: brfalse.s IL_002E
// IL_0021: ldloc.0 // user
// IL_0022: ldloc.1 // value
// IL_0023: castclass System.String
// IL_0028: callvirt UserQuery+User.set_Name
il.Emit(OpCodes.Ldloc, valueLoacl);
il.Emit(OpCodes.Isinst, typeof(System.DBNull));
il.Emit(OpCodes.Ldnull);
var tmpLoacl = il.DeclareLocal(typeof(int));
il.Emit(OpCodes.Cgt_Un);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Stloc,tmpLoacl);
il.Emit(OpCodes.Ldloc,tmpLoacl);
var labelFalse = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S,labelFalse);
il.Emit(OpCodes.Ldloc, returnValueLocal);
il.Emit(OpCodes.Ldloc, valueLoacl);
if (p.PropertyType.IsValueType)
il.Emit(OpCodes.Unbox_Any, p.PropertyType);
else
il.Emit(OpCodes.Castclass, p.PropertyType);
il.Emit(OpCodes.Callvirt, p.SetMethod);
il.MarkLabel(labelFalse);
index++;
}
// IL_0053: ldloc.0 // user
// IL_0054: stloc.s 04 //不需要
// IL_0056: br.s IL_0058
// IL_0058: ldloc.s 04 //不需要
// IL_005A: ret
il.Emit(OpCodes.Ldloc, returnValueLocal);
il.Emit(OpCodes.Ret);
var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType);
return (Func<IDataReader, object>)dm.CreateDelegate(funcType);
}
private static void EmitInt32(ILGenerator il, int value)
{
switch (value)
{
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
default:
if (value >= -128 && value <= 127)
{
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
break;
}
}
}
這邊Emit的細節概念非常的多,這邊無法全部都講解,先挑出重要概念講解
在Emit if/else需要使用Label定位,告知編譯器條件為true/false時要跳到哪個位子,舉例 : 「boolean轉整數」,假設要簡單將Boolean轉換成Int,C#代碼可以用「如果是True返回1否則返回0」邏輯來寫:
public static int BoolToInt(bool input) => input ? 1 : 0;
當轉成Emit寫法的時候,需要以下邏輯 :
(注意,這時候Label位子還沒確定)
符合條件
要運行區塊的前一行
,使用MarkLabel方法標記Label的位子
。最後寫出的C# Emit代碼 :
public class Program
{
public static void Main(string[] args)
{
var func = CreateFunc();
Console.WriteLine(func(true)); //1
Console.WriteLine(func(false)); //0
}
static Func<bool, int> CreateFunc()
{
var dm = new DynamicMethod("Test" + Guid.NewGuid().ToString(), typeof(int), new[] { typeof(bool) });
var il = dm.GetILGenerator();
var labelTrue = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Brtrue_S, labelTrue);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
il.MarkLabel(labelTrue);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(bool), typeof(int));
return (Func<bool, int>)dm.CreateDelegate(funcType);
}
}
這邊可以發現Emit版本
優點 :
缺點 :
接著來看Dapper作者的建議,現在一般專案當中沒有必要使用Emit,使用Expression + Func/Action已經可以解決大部分動態方法的需求,尤其是Expression支援Block等方法情況。連結 c# - What's faster: expression trees or manually emitting IL
話雖如此,但有一些厲害的開源專案就是使用Emit管理細節,如果想看懂它們,就需要基礎的Emit IL概念
。